]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
util: add Util.spawn and friends
authorDan Winship <danw@gnome.org>
Wed, 17 Nov 2010 16:43:08 +0000 (11:43 -0500)
committerDan Winship <danw@gnome.org>
Thu, 13 Jan 2011 17:14:40 +0000 (12:14 -0500)
Add Util.spawn, Util.spawnCommandLine, and Util.spawnDesktop for
spawning a command/argv/.desktop file in the background, automatically
handling errors via MessageTray.SystemNotificationSource(), and
Util.trySpawn, Util.trySpawnCommandLine, and Utils.trySpawnDesktop
that don't do automatic error handling (but do at least clean up the
error message in the exception a bit).

Update various other bits of code around the shell to use the new
methods.

https://bugzilla.gnome.org/show_bug.cgi?id=635089

js/misc/util.js
js/ui/main.js
js/ui/messageTray.js
js/ui/placeDisplay.js
js/ui/runDialog.js
js/ui/status/accessibility.js
js/ui/status/power.js
js/ui/status/volume.js
js/ui/statusMenu.js
po/POTFILES.in

index 0d1d5c46432b3ab287b1663bfbbd994f16605a3a..e61f0dd94fd770c4c444607c5ba5a7751641fce3 100644 (file)
@@ -1,5 +1,16 @@
 /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
 
+const Gdk = imports.gi.Gdk;
+const Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
+const Shell = imports.gi.Shell;
+
+const Main = imports.ui.main;
+const MessageTray = imports.ui.messageTray;
+
+const Gettext = imports.gettext.domain('gnome-shell');
+const _ = Gettext.gettext;
+
 /* http://daringfireball.net/2010/07/improved_regex_for_matching_urls */
 const _urlRegexp = new RegExp('\b(([a-z][\w-]+:(/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)([^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»“”‘’]))', 'gi');
 
@@ -17,3 +28,123 @@ function findUrls(str) {
         res.push({ url: match[0], pos: match.index });
     return res;
 }
+
+// spawn:
+// @argv: an argv array
+//
+// Runs @argv in the background, handling any errors that occur
+// when trying to start the program.
+function spawn(argv) {
+    try {
+        trySpawn(argv);
+    } catch (err) {
+        _handleSpawnError(argv[0], err);
+    }
+}
+
+// spawnCommandLine:
+// @command_line: a command line
+//
+// Runs @command_line in the background, handling any errors that
+// occur when trying to parse or start the program.
+function spawnCommandLine(command_line) {
+    try {
+        let [success, argc, argv] = GLib.shell_parse_argv(command_line);
+        trySpawn(argv);
+    } catch (err) {
+        _handleSpawnError(command_line, err);
+    }
+}
+
+// spawnDesktop:
+// @id: a desktop file ID
+//
+// Spawns the desktop file identified by @id using startup notification,
+// etc, handling any errors that occur when trying to find or start
+// the program.
+function spawnDesktop(id) {
+    try {
+        trySpawnDesktop(id);
+    } catch (err) {
+        _handleSpawnError(id, err);
+    }
+}
+
+// trySpawn:
+// @argv: an argv array
+//
+// Runs @argv in the background. If launching @argv fails,
+// this will throw an error.
+function trySpawn(argv)
+{
+    try {
+        GLib.spawn_async(null, argv, null,
+                         GLib.SpawnFlags.SEARCH_PATH,
+                         null, null);
+    } catch (err) {
+        // The exception from gjs contains an error string like:
+        //   Error invoking GLib.spawn_command_line_async: Failed to
+        //   execute child process "foo" (No such file or directory)
+        // We are only interested in the part in the parentheses. (And
+        // we can't pattern match the text, since it gets localized.)
+        err.message = err.message.replace(/.*\((.+)\)/, '$1');
+        throw err;
+    }
+}
+
+// trySpawnCommandLine:
+// @command_line: a command line
+//
+// Runs @command_line in the background. If launching @command_line
+// fails, this will throw an error.
+function trySpawnCommandLine(command_line) {
+    let success, argc, argv;
+
+    try {
+        [success, argc, argv] = GLib.shell_parse_argv(command_line);
+    } catch (err) {
+        // Replace "Error invoking GLib.shell_parse_argv: " with
+        // something nicer
+        err.message = err.message.replace(/[^:]*: /, _("Could not parse command:") + "\n");
+        throw err;
+    }
+
+    trySpawn(argv);
+}
+
+// trySpawnDesktop:
+// @id: a desktop file ID
+//
+// Spawns the desktop file identified by @id using startup notification.
+// On error, throws an exception.
+function trySpawnDesktop(id) {
+    let app;
+
+    // shell_app_system_load_from_desktop_file() will end up returning
+    // a stupid error message if the desktop file doesn't exist, but
+    // that's the only case it returns an error for, so we just
+    // substitute our own error in instead
+    try {
+        app = Shell.AppSystem.get_default().load_from_desktop_file(id + '.desktop');
+    } catch (err) {
+        throw new Error(_("No such application"));
+    }
+
+    try {
+        app.launch();
+    } catch(err) {
+        // see trySpawn
+        err.message = err.message.replace(/.*\((.+)\)/, '$1');
+        throw err;
+    }
+}
+
+function _handleSpawnError(command, err) {
+    let title = _("Execution of '%s' failed:").format(command);
+
+    let source = new MessageTray.SystemNotificationSource();
+    Main.messageTray.add(source);
+    let notification = new MessageTray.Notification(source, title, err.message);
+    notification.setTransient(true);
+    source.notify(notification);
+}
index 8574fe7011cc651f5d1fbf2273837dbb27be5892..1ee8065163c3a784108c37686048b464a0e2006e 100644 (file)
@@ -39,6 +39,7 @@ const WindowManager = imports.ui.windowManager;
 const Magnifier = imports.ui.magnifier;
 const XdndHandler = imports.ui.xdndHandler;
 const StatusIconDispatcher = imports.ui.statusIconDispatcher;
+const Util = imports.misc.util;
 
 const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
 DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff);
@@ -317,11 +318,8 @@ function _globalKeyPressHandler(actor, event) {
     if (action == Meta.KeyBindingAction.COMMAND_SCREENSHOT) {
         let gconf = GConf.Client.get_default();
         let command = gconf.get_string('/apps/metacity/keybinding_commands/command_screenshot');
-        if (command != null && command != '') {
-            let [ok, len, args] = GLib.shell_parse_argv(command);
-            let p = new Shell.Process({'args' : args});
-            p.run();
-        }
+        if (command != null && command != '')
+            Util.spawnCommandLine(command);
         return true;
     }
 
index e085421ef8e716c1b73d8d7631740e5df9f62286..57f5acf026a9a46081dd6322a1a80b3e92d786a2 100644 (file)
@@ -106,8 +106,7 @@ URLHighlighter.prototype = {
                     return true;
                 } catch (e) {
                     // TODO: remove this after gnome 3 release
-                    let p = new Shell.Process({ 'args' : ['gvfs-open', url] });
-                    p.run();
+                    Util.spawn(['gvfs-open', url]);
                     return true;
                 }
             }
index fb96149c9f43f502464a290c3eab7c697b6d4236..9b7134b8911d2bf3c2f08f9f6cce567c272f6fd4 100644 (file)
@@ -14,6 +14,7 @@ const _ = Gettext.gettext;
 const DND = imports.ui.dnd;
 const Main = imports.ui.main;
 const Search = imports.ui.search;
+const Util = imports.misc.util;
 
 const NAUTILUS_PREFS_DIR = '/apps/nautilus/preferences';
 const DESKTOP_IS_HOME_KEY = NAUTILUS_PREFS_DIR + '/desktop_is_home_dir';
@@ -163,7 +164,7 @@ PlacesManager.prototype = {
                                      icon_size: size });
             },
             function () {
-                new Shell.Process({ args: ['nautilus-connect-server'] }).run();
+                Util.spawn(['nautilus-connect-server']);
             });
 
         let networkApp = null;
index d09639ceb833b1df90f31ce4438d16bde398e196..955f1e777583057453174fdcd7480f1312d709d6 100644 (file)
@@ -14,6 +14,7 @@ const _ = Gettext.gettext;
 const Lightbox = imports.ui.lightbox;
 const Main = imports.ui.main;
 const Tweener = imports.ui.tweener;
+const Util = imports.misc.util;
 
 const MAX_FILE_DELETED_BEFORE_INVALID = 10;
 
@@ -354,9 +355,7 @@ RunDialog.prototype = {
             try {
                 if (inTerminal)
                     command = 'gnome-terminal -x ' + input;
-                let [ok, len, args] = GLib.shell_parse_argv(command);
-                let p = new Shell.Process({ 'args' : args });
-                p.run();
+                Util.trySpawnCommandLine(command);
             } catch (e) {
                 // Mmmh, that failed - see if @input matches an existing file
                 let path = null;
@@ -374,13 +373,8 @@ RunDialog.prototype = {
                                                         global.create_app_launch_context());
                 } else {
                     this._commandError = true;
-                    // The exception contains an error string like:
-                    // Error invoking Shell.run: Failed to execute child
-                    // process "foo" (No such file or directory)
-                    // We are only interested in the actual error, so parse
-                    //that out.
-                    let m = /.+\((.+)\)/.exec(e);
-                    let errorStr = _("Execution of '%s' failed:").format(command) + '\n' + m[1];
+
+                    let errorStr = _("Execution of '%s' failed:").format(command) + '\n' + e.message;
                     this._errorMessage.set_text(errorStr);
 
                     this._errorBox.show();
index f96e193a845b680b8ae4298321070879e818a910..11a95f37364303e1e341b4799a29d6af0d85b1b6 100644 (file)
@@ -12,6 +12,7 @@ const St = imports.gi.St;
 
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
+const Util = imports.misc.util;
 
 const Gettext = imports.gettext.domain('gnome-shell');
 const _ = Gettext.gettext;
@@ -108,8 +109,7 @@ ATIndicator.prototype = {
 
         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
         this.menu.addAction(_("Universal Access Settings"), function() {
-            let p = new Shell.Process({ args: ['gnome-control-center','universal-access'] });
-            p.run();
+            Util.spawnDesktop('gnome-universal-access-panel');
         });
     },
 
index 3a909c4cd0fdf51532fea88a6d51ff289ca14a08..fe787f790d29f8e3ca966c5b50d4e8ca9ec667dd 100644 (file)
@@ -10,6 +10,7 @@ const St = imports.gi.St;
 
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
+const Util = imports.misc.util;
 
 const Gettext = imports.gettext.domain('gnome-shell');
 const _ = Gettext.gettext;
@@ -83,7 +84,7 @@ Indicator.prototype = {
         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
 
         this.menu.addAction(_("Power Settings"),function() {
-            GLib.spawn_command_line_async('gnome-control-center power');
+            Util.spawnDesktop('gnome-power-panel');
         });
 
         this._proxy.connect('Changed', Lang.bind(this, this._devicesChanged));
@@ -134,8 +135,7 @@ Indicator.prototype = {
                 this._batteryItem.actor.reactive = true;
                 this._batteryItem.actor.can_focus = true;
                 this._batteryItem.connect('activate', function(item) {
-                    let p = new Shell.Process({ args: ['gnome-power-statistics', '--device', device_id] });
-                    p.run();
+                    Util.spawn(['gnome-power-statistics', '--device', device_id]);
                 });
             } else {
                 // virtual device
@@ -164,8 +164,7 @@ Indicator.prototype = {
 
                 let item = new DeviceItem (devices[i]);
                 item.connect('activate', function() {
-                    let p = new Shell.Process({ args: ['gnome-power-statistics', '--device', device_id] });
-                    p.run();
+                    Util.spawn(['gnome-power-statistics', '--device', device_id]);
                 });
                 this._deviceItems.push(item);
                 this.menu.addMenuItem(item, this._otherDevicePosition + position);
@@ -198,7 +197,7 @@ Indicator.prototype = {
     _checkError: function(error) {
         if (!this._restarted && error && error.message.match(/org\.freedesktop\.DBus\.Error\.(UnknownMethod|InvalidArgs)/)) {
             GLib.spawn_command_line_sync('pkill -f "^gnome-power-manager$"');
-            GLib.spawn_command_line_async('gnome-power-manager');
+            Util.spawn(['gnome-power-manager']);
             this._restarted = true;
         }
     }
index 63f76fe0d2423adf23bbf73c4d2ada84c1d37329..9211968f96a799c6bf910257705cd6870516c7e0 100644 (file)
@@ -11,6 +11,7 @@ const St = imports.gi.St;
 
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
+const Util = imports.misc.util;
 
 const Gettext = imports.gettext.domain('gnome-shell');
 const _ = Gettext.gettext;
@@ -60,8 +61,7 @@ Indicator.prototype = {
 
         this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
         this.menu.addAction(_("Sound Settings"), function() {
-            let p = new Shell.Process({ args: ['gnome-control-center', 'sound'] });
-            p.run();
+            Util.spawnDesktop('gnome-sound-panel');
         });
 
         this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
index e2d13c68d41844c9bfb81137c628b3b9884664ba..f834aad49726a0740ccf7c113484f05e86cf3f86 100644 (file)
@@ -12,6 +12,7 @@ const GnomeSession = imports.misc.gnomeSession;
 const Main = imports.ui.main;
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
+const Util = imports.misc.util;
 
 // Adapted from gdm/gui/user-switch-applet/applet.c
 //
@@ -31,7 +32,7 @@ StatusMenuButton.prototype = {
         this.actor.set_child(box);
 
         this._gdm = Gdm.UserManager.ref_default();
-        this._gdm.queue_load()
+        this._gdm.queue_load();
 
         this._user = this._gdm.get_user(GLib.get_user_name());
         this._presence = new GnomeSession.Presence();
@@ -153,17 +154,17 @@ StatusMenuButton.prototype = {
 
     _onMyAccountActivate: function() {
         Main.overview.hide();
-        this._spawn(['gnome-control-center', 'user-accounts']);
+        Util.spawnDesktop('gnome-user-accounts-panel');
     },
 
     _onPreferencesActivate: function() {
         Main.overview.hide();
-        this._spawn(['gnome-control-center', '-o']);
+        Util.spawnDesktop('gnome-control-center');
     },
 
     _onLockScreenActivate: function() {
         Main.overview.hide();
-        this._spawn(['gnome-screensaver-command', '--lock']);
+        Util.spawn(['gnome-screensaver-command', '--lock']);
     },
 
     _onLoginScreenActivate: function() {
@@ -174,19 +175,11 @@ StatusMenuButton.prototype = {
 
     _onQuitSessionActivate: function() {
         Main.overview.hide();
-        this._spawn(['gnome-session-save', '--logout-dialog']);
+        Util.spawn(['gnome-session-save', '--logout-dialog']);
     },
 
     _onShutDownActivate: function() {
         Main.overview.hide();
-        this._spawn(['gnome-session-save', '--shutdown-dialog']);
-    },
-
-    _spawn: function(args) {
-        // FIXME: once Shell.Process gets support for signalling
-        // errors we should pop up an error dialog or something here
-        // on failure
-        let p = new Shell.Process({'args' : args});
-        p.run();
+        Util.spawn(['gnome-session-save', '--shutdown-dialog']);
     }
 };
index cb9c39f0b5f86a0fcc22649df008ac3d8e803823..255a3d65185d8dde8b1f13c6f32d7c1c4503beec 100644 (file)
@@ -1,6 +1,7 @@
 data/gnome-shell.desktop.in.in
 data/org.gnome.shell.gschema.xml.in
 data/org.gnome.accessibility.magnifier.gschema.xml.in
+js/misc/util.js
 js/ui/appDisplay.js
 js/ui/appFavorites.js
 js/ui/dash.js